/************************************************************************
 * @file PluginDependencyResolver.cpp
 * @author Ian Chen
 * @version 1.0
 *
 * @section LICENSE
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
 * General Public License for more details at
 * http://www.gnu.org/licenses/lgpl.html
 *
 ************************************************************************/
#include <algorithm>

#include <QLibrary>
#include <QMap>

#include <plugin/util/AbstractSecurityContextHandler.h>
#include <plugin/util/Framework.h>
#include <plugin/util/PluginDependencyResolver.h>
#include <plugin/util/PluginDescriptionParser.h>

namespace antu
{
namespace plugin
{
// Sort description by start level
bool sortDescription(PluginDescriptionSharedPtr a, PluginDescriptionSharedPtr b)
{
    return a->startLevel() < b->startLevel();
}

PluginDependencyResolver::PluginDependencyResolver()
{
}

void PluginDependencyResolver::resolve(const QString& appGroup, const QString &configPath,
                                       vector<PluginDescriptionSharedPtr>& resolvedDescriptions)
{
    vector<PluginDescriptionSharedPtr> pluginDescriptions;
    PluginDescriptionParser::parseDirectory(configPath, pluginDescriptions);

    PluginDependencyResolver::resolveDependencies(appGroup, pluginDescriptions, resolvedDescriptions);
}

void PluginDependencyResolver::resolveDependencies(const QString& applicationGroup,
                                                   const vector<PluginDescriptionSharedPtr> &descriptions,
                                                   vector<PluginDescriptionSharedPtr>& resolvedDescriptions)
{
    // all unchecked plugin description
    QMap<QString, PluginDescriptionSharedPtr> mappedDescriptions;

    // sort all plugin description by name
    for (auto iter = descriptions.begin();
         iter != descriptions.end(); iter++)
    {
        PluginDescriptionSharedPtr description = *iter;
        mappedDescriptions[description->code()] = description;
    }


    vector<PluginDescriptionSharedPtr> unresolvedDescriptions;
    PluginDependencyResolver::checkDependencies(applicationGroup, mappedDescriptions, resolvedDescriptions, unresolvedDescriptions);
}

void PluginDependencyResolver::checkDependencies(const QString& applicationGroup,
                                                 QMap<QString, PluginDescriptionSharedPtr> &sortedDescriptions,
                                                 vector<PluginDescriptionSharedPtr> &resolvedDescriptions,
                                                 vector<PluginDescriptionSharedPtr> &unresolvedDescriptions)
{
    // all checked and allowed plugin description
    QMap<QString, PluginDescriptionSharedPtr> organizedDescriptions;
	QMap<QString, PluginDescriptionSharedPtr> pluginsMissingRequirements;
    typedef pair<QString, PluginDescriptionSharedPtr> DescriptionPair;

    int count = 0;
    do
    {
        QMap<QString, PluginDescriptionSharedPtr>::iterator iter =
            sortedDescriptions.begin();
        vector<PluginDescriptionSharedPtr> tempResult;

        // for each unchecked plugin description, do
        for (; iter != sortedDescriptions.end(); iter++)
        {
            PluginDescriptionSharedPtr description = iter.value();

            // if the plugin does not belong to current framework's application group,
            // and is not a universal plugin, skip
            if (!description->isSupportedApplicationGroup(applicationGroup))
            {
                Framework::Instance.logger()->debug(__FILE__, __LINE__, "PluginDependencyResolver", "checkDependencies", description->path() + " does not support current application group.");
                continue;
            }
            if (!Framework::Instance.securityContextHandler()->isAllowed(description->code()))
            {
                Framework::Instance.logger()->debug(__FILE__, __LINE__, "PluginDependencyResolver", "checkDependencies", description->path() + " is not allowed by current security manager.");
                continue;
            }

            bool itemMissing = false;

            int requirementCount = description->requirementCount();
            // check to see if all required plugins of the current plugin is already checked.
            for (int i = 0; i < requirementCount; i++)
            {
                PluginRequirement requirement = description->requirementAt(i);
                QMap<QString, PluginDescriptionSharedPtr>::iterator item = organizedDescriptions.find(requirement.code());

                // if a required plugin not found, this plugin is not to be loaded
                if (item == organizedDescriptions.end())
                {
					pluginsMissingRequirements.insert(description->code(), description);
                    //Framework::Instance.logger()->debug(__FILE__, __LINE__, "PluginDependencyResolver", "checkDependencies", description->path() + " requirement: " + requirement.code() + " not found.");
                    itemMissing = true;
                    break;
                }
                else // if a required plugin is found, check its compatible version
                {
                    PluginDescriptionSharedPtr requiredPlugin = item.value();
                    Framework::Instance.logger()->debug(__FILE__, __LINE__, "PluginDependencyResolver", "checkDependencies", description->path() + " required version: " + requirement.requiredVersion().toString() + " not met.");
                    if (requiredPlugin->minimumCompatibleVersion() > requirement.requiredVersion())
                    {
                        itemMissing = true;
                        break;
                    }
                }
            }

            // if all required plugins are in checked set, add current plugin into checked set
            if (!itemMissing)
            {
                QLibrary library;
#ifdef Q_OS_UNIX
                library.setFileName(tr("lib") + description->path());
#else
                library.setFileName(description->path());
#endif

                if (library.load())
                {
                    pFactoryInitFunc pInitFunc = (pFactoryInitFunc)library.resolve(FACTORY_INIT_FUNC);
                    if (pInitFunc == nullptr)
                    {
                        library.unload();
                        Framework::Instance.logger()->debug(__FILE__, __LINE__, "PluginDependencyResolver", "checkDependencies", description->path() + " pointer to InitFactory() not found.");
                    }
                    else
                    {
                        (*pInitFunc)(description->code());
                        tempResult.push_back(description);
                        Framework::Instance.logger()->info(__FILE__, __LINE__, "PluginDependencyResolver", "checkDependencies", description->path() + " loaded.");
                    }
                }
                else
                {
                    Framework::Instance.logger()->debug(__FILE__, __LINE__, "PluginDependencyResolver", "checkDependencies", description->path() + " library not found.");
                }
            }
        }
        // sort plugins by start level
        std::sort(tempResult.begin(), tempResult.end(), sortDescription);
        count = tempResult.size();
        for (int i = 0; i < count; i++)
        {
			PluginDescriptionSharedPtr desc = tempResult.at(i);
            // add to result
            resolvedDescriptions.push_back(desc);

			if (pluginsMissingRequirements.find(desc->code()) != pluginsMissingRequirements.end())
			{
				pluginsMissingRequirements.remove(desc->code());
			}

            Framework::Instance.logger()->debug(__FILE__, __LINE__, "PluginDependencyResolver", "checkDependencies", desc->path() + " validated.");
            // remove from unchecked set
            sortedDescriptions.remove(desc->code());
            // add to checked set
            organizedDescriptions.insert(desc->code(), desc);
        }
        tempResult.clear();
    } while (count > 0);

    if (sortedDescriptions.size() > 0)
    {
        QMap<QString, PluginDescriptionSharedPtr>::iterator beginIter = sortedDescriptions.begin();
        QMap<QString, PluginDescriptionSharedPtr>::iterator endIter = sortedDescriptions.end();

        for (; beginIter != endIter; beginIter++)
        {
			PluginDescriptionSharedPtr desc = beginIter.value();
            unresolvedDescriptions.push_back(desc);

			if (pluginsMissingRequirements.find(desc->code()) != pluginsMissingRequirements.end())
			{
				Framework::Instance.logger()->debug(__FILE__, __LINE__, "PluginDependencyResolver", "checkDependencies", desc->path() + " requirements not met, not loaded.");
			}
			else
			{
				Framework::Instance.logger()->debug(__FILE__, __LINE__, "PluginDependencyResolver", "checkDependencies", desc->path() + " not loaded.");
			}
        }
    }
}

void PluginDependencyResolver::checkDependencies(const QString& applicationGroup,
                                                 const vector<PluginDescriptionSharedPtr> &descriptions,
                                                 const vector<PluginDescriptionSharedPtr> &newDescriptions,
                                                 vector<PluginDescriptionSharedPtr> &newDependencies,
                                                 vector<PluginDescriptionSharedPtr> &unresolvedPlugins)
{
    // all unchecked plugin description
    QMap<QString, PluginDescriptionSharedPtr> sortedDescriptions;
    typedef pair<QString, PluginDescriptionSharedPtr> DescriptionPair;

    // sort all plugin description by code
    for (vector<PluginDescriptionSharedPtr>::const_iterator iter = descriptions.begin();
         iter != descriptions.end(); iter++)
    {
        sortedDescriptions[(*iter)->code()] = *iter;
    }

    for (vector<PluginDescriptionSharedPtr>::const_iterator iter = newDescriptions.begin();
         iter != newDescriptions.end(); iter++)
    {
        sortedDescriptions[(*iter)->code()] = (*iter);
    }

    PluginDependencyResolver::checkDependencies(applicationGroup, sortedDescriptions, newDependencies, unresolvedPlugins);
}

void PluginDependencyResolver::reorder(const vector<PluginDescriptionSharedPtr> &descriptions, vector<PluginDescriptionSharedPtr> &sortedDescriptions)
{
    // all unchecked plugin description
    map<QString, PluginDescriptionSharedPtr> mappedDescriptions;
    typedef pair<QString, PluginDescriptionSharedPtr> DescriptionPair;

    // sort all plugin description by name
    for (vector<PluginDescriptionSharedPtr>::const_iterator iter = descriptions.begin();
         iter != descriptions.end(); iter++)
    {
        mappedDescriptions.insert(DescriptionPair((*iter)->code(), (*iter)));
    }

    //vector<PluginDescriptionSharedPtr> unmappedDescriptions;
    map<QString, PluginDescriptionSharedPtr> organizedDescriptions;
    int count = 0;
    do
    {
        map<QString, PluginDescriptionSharedPtr>::iterator iter =
            mappedDescriptions.begin();
        vector<PluginDescriptionSharedPtr> tempResult;

        // for each unchecked plugin description, do
        for (; iter != mappedDescriptions.end(); iter++)
        {
            PluginDescriptionSharedPtr description = (*iter).second;

            bool itemMissing = false;

            int requirementCount = description->requirementCount();
            // check to see if all required plugins of the current plugin is already checked.
            for (int i = 0; i < requirementCount; i++)
            {
                PluginRequirement requirement = description->requirementAt(i);
                map<QString, PluginDescriptionSharedPtr>::iterator item = organizedDescriptions.find(requirement.code());

                // if a required plugin not found, this plugin is not to be loaded
                if (item == organizedDescriptions.end())
                {
                    itemMissing = true;
                    break;
                }
            }

            // if all required plugins are in checked set, add current plugin into checked set
            if (!itemMissing)
            {
                tempResult.push_back(description);
            }
        }
        // sort plugins by start level
        std::sort(tempResult.begin(), tempResult.end(), sortDescription);
        count = tempResult.size();
        for (int i = 0; i < count; i++)
        {
            // add to result
            sortedDescriptions.push_back(tempResult.at(i));
            // remove from unchecked set
            mappedDescriptions.erase(tempResult.at(i)->code());
            // add to checked set
            organizedDescriptions.insert( DescriptionPair(tempResult.at(i)->code(), tempResult.at(i)) );
        }
        tempResult.clear();
    } while (count > 0);
}

}
}

